package ripple.test.tools;

import ripple.common.DebugTool;
import ripple.common.entity.AbstractMessage;
import ripple.server.RippleServer;
import ripple.server.core.overlay.FicusOverlay;

import java.util.*;

public class Logger {

    public Manager getManager() {
        return manager;
    }

    public void setManager(Manager manager) {
        this.manager = manager;
    }

    Manager manager;
    public Logger(Manager manager) {
        this.setManager(manager);
    }

    public Set<String> getTopicSet() {
        Set<String> topicSet = new HashSet<>();
        for (RippleServer rippleServer : getManager().getServerList()) {
            topicSet.addAll(rippleServer.getNode().getStorage().getMessageService().getTopics());
        }
        return topicSet;
    }

    public long getAllNodeAveragePushTime() {
        Map<UUID, MessageRecord> messageRecordMap = getMessageRecordMap(this.getManager().getServerList());
        return getAveragePushTime(messageRecordMap);
    }
    public long getEdgeNodeAveragePushTime() {
        Map<UUID, MessageRecord> messageRecordMap = getMessageRecordMap(this.getManager().getEdgeServerList());
        return getAveragePushTime(messageRecordMap);
    }
    public long getCloudNodeAveragePushTime() {
        Map<UUID, MessageRecord> messageRecordMap = getMessageRecordMap(this.getManager().getCloudServerList());
        return getAveragePushTime(messageRecordMap);
    }

    public List<List<List<Long>>> getAllPushTimes() {
        Set<String> topicSet = this.getTopicSet();


        List<List<List<Long>>> pushTime = new ArrayList<>();
        for (String topic : topicSet) {
            pushTime.add(this.getPushTimes(topic));
        }
        return pushTime;
    }

    public List<List<List<Long>>> getEdgePushTimes() {
        Set<String> topicSet = this.getTopicSet();

        Map<UUID, MessageRecord> messageRecordMap = this.getMessageRecordMap(this.getManager().getEdgeServerList());
        List<List<List<Long>>> pushTime = new ArrayList<>();
        for (String topic : topicSet) {
            pushTime.add(this.getEdgePushTimes(topic));
        }
        return pushTime;
    }

    private Map<UUID, MessageRecord> getMessageRecordMap(List<RippleServer> serverList) {
        List<AbstractMessage> receivedMessages = new ArrayList<>();
        for (RippleServer server : serverList) {
            for (AbstractMessage message : server.getNode().getStorage().getMessageService().getAllMessage()) {
                if (server.getId() != message.getLastUpdateServerId()) {
                    receivedMessages.add(message);
//                    DebugTool.write(DebugTool.buildInfo(this.getClass(), "getMessageRecordMap\n"
//                            , "source id: " + message.getLastUpdateServerId()
//                                    + " server id: " + server.getId()
//                                    + ", push time: " + (message.getStore().getTime() -message.getLastUpdate().getTime())
//                                    + " UUID: " + message.getUuid()
//                                    + ", create time: " + message.getLastUpdate().getTime()
//                                    + ", store time: " + message.getStore().getTime()
//                    ));
                }
            }
        }
        return this.buildMessageRecords(receivedMessages);
    }

    private Map<UUID, MessageRecord> buildMessageRecords(List<AbstractMessage> messages) {
        Map<UUID, MessageRecord> messageRecordMap = new HashMap<>();
//        DebugTool.write("[buildMessageRecords] " + "message num: " + messages.size());
        for (AbstractMessage message : messages) {

            if (messageRecordMap.containsKey(message.getUuid())) {
                messageRecordMap.get(message.getUuid()).getStoreTimes().add(message.getStore().getTime());
            } else {
                messageRecordMap.put(message.getUuid(), new MessageRecord());
                messageRecordMap.get(message.getUuid()).setCreatedTime(message.getLastUpdate().getTime());
                messageRecordMap.get(message.getUuid()).setStoreTimes(new ArrayList<>());
                messageRecordMap.get(message.getUuid()).getStoreTimes().add(message.getStore().getTime());
            }
        }
        return messageRecordMap;
    }

    public List<List<List<Long>>> getCloudPushTimes() {
        Set<String> topicSet = new HashSet<>();
        for (RippleServer rippleServer : getManager().getServerList()) {
            if (rippleServer.getNode().getId() % FicusOverlay.MAX_SERVER_COUNT > FicusOverlay.MAX_CLOUD_GROUP) {
                continue;
            }
            topicSet.addAll(rippleServer.getNode().getStorage().getMessageService().getTopics());
        }


        List<List<List<Long>>> pushTime = new ArrayList<>();
        for (String topic : topicSet) {
            pushTime.add(this.getCloudPushTimes(topic));
        }
        return pushTime;
    }

    public List<List<Long>> getPushTimes(String topic) {
        return getPushTimes(topic, this.getManager().getServerList());
    }

    public List<List<Long>> getEdgePushTimes(String topic) {
        return getPushTimes(topic, this.getManager().getEdgeServerList());
    }

    public List<List<Long>> getCloudPushTimes(String topic) {
        return getPushTimes(topic, this.getManager().getCloudServerList());
    }

    public List<List<Long>> getPushTimes(String topic, List<RippleServer> serverList) {
        List<AbstractMessage> messages = new ArrayList<>();
        for (RippleServer rippleServer : serverList) {
            messages.addAll(rippleServer.getNode().getStorage().getMessageService().findMQMessages(topic));
        }
        Map<UUID, List<Long>> updateMap = new HashMap<>();
        for (AbstractMessage message : messages) {
            updateMap.putIfAbsent(message.getUuid(), new ArrayList<>());
            updateMap.get(message.getUuid()).add(message.getStore().getTime());
        }

        List<List<Long>> pushTime = new ArrayList<>();
        for (List<Long> updateList : updateMap.values()) {
            updateList.sort(new Comparator<Long>() {
                @Override
                public int compare(Long o1, Long o2) {
                    return o1.compareTo(o2);
                }
            });
            List<Long> pushTimeList = new ArrayList<>();
//            long base = updateList.get(0);
            for(int i = 1; i < updateList.size(); i++) {
//                DebugTool.write("getPushTimes: [updateList.get(i): " + updateList.get(i) + "], [updateList.get(0): " + updateList.get(0) + "]");
//                DebugTool.write("updateList.get(i): " + updateList.get(i));
//                DebugTool.write("base: " + base);
//                DebugTool.write("updateList.size(): " + updateList.size());
//                DebugTool.write("updateList.get(i) - base: " + (updateList.get(i) - base));

                pushTimeList.add(updateList.get(i) - updateList.get(0));

            }
            pushTime.add(pushTimeList);
        }
        return pushTime;
    }

    public long getAveragePushTime(Map<UUID, MessageRecord> messageRecordMap) {
        long timeSum = 0;
        long messageCount = 0;

        for (MessageRecord messageRecord : messageRecordMap.values()) {
            for (Long storeTime : messageRecord.getStoreTimes()) {
                timeSum += storeTime - messageRecord.getCreatedTime();
                messageCount++;
//                DebugTool.write("[Logger] [getAveragePushTime] "
//                        + "pushTime: " + (storeTime - messageRecord.getCreatedTime())
//                        + ", messageCount: " + messageCount
//                        + ", store time: " + storeTime
//                        + ", create time: " + messageRecord.getCreatedTime()
//                        );
            }
        }

        if (messageCount == 0) {
            DebugTool.write(("[Logger] In method getAveragePushTime, messageCount = 0"));
            return -1;
        }
//        DebugTool.write("getAverage: [timeSum: " + timeSum + "], [messageCount: " + messageCount + "]");
        return timeSum / messageCount;
    }

    public long getAverage(List<List<Long>> pushTime) {

        long timeSum = 0;
        long messageCount = 0;
        for (List<Long> pushTimeList : pushTime) {
            for (long eachTime : pushTimeList) {
//                DebugTool.write("getAverage eachTime: " + eachTime);
                timeSum += eachTime;
                messageCount++;
            }
        }
        if (messageCount == 0) {
            DebugTool.write(("[Logger] In method getAverage, messageCount = 0"));
            return 0;
        }
//        DebugTool.write("getAverage: [timeSum: " + timeSum + "], [messageCount: " + messageCount + "]");
        return timeSum / messageCount;
    }

    public long getAllAverage(List<List<List<Long>>> pushTime) {
        long timeSum = 0;
        long messageCount = 0;
        for (List<List<Long>> pushTimeList1 : pushTime) {
            for (List<Long> pushTimeList2 : pushTimeList1) {
                for (long eachTime : pushTimeList2) {
//                    DebugTool.write("getAllAverage eachTime: " + eachTime);
                    timeSum += eachTime;
                    messageCount++;
                }
            }
        }
//        DebugTool.write("getAllAverage: [timeSum: " + timeSum + "], [messageCount: " + messageCount + "]");
        if (messageCount == 0) {
            DebugTool.write(("[Logger] In method getAllAverage, messageCount = 0"));
            return 0;
        }
        return timeSum / messageCount;
    }

    public void recordResult(String testName) {
        DebugTool.write("[" + testName + "] all node push average time: "
                + String.valueOf(this.getAllNodeAveragePushTime()));
        DebugTool.write("[" + testName + "] edge node push average time: "
                +  String.valueOf(this.getEdgeNodeAveragePushTime()));
        DebugTool.write("[" + testName + "] cloud node push average time: "
                +  String.valueOf(this.getCloudNodeAveragePushTime()));
    }

    public void clean() {
        for (RippleServer server : this.getManager().getServerList()) {
            server.cleanDB();
        }
    }
}
